#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/semaphore.h>

#define KMAX_LEN 64

static int major = 101;
static int minor= 0;
static dev_t devno;
struct device *class_dev = NULL;
struct class *cls;
char kbuf[KMAX_LEN + 1] = "kernel";
struct semaphore sem;

static int hello_open(struct inode *inode, struct file *filep)
{
	printk("hello open() \n");

	if(down_interruptible(&sem))
		return -ERESTARTSYS;
	printk("dowm sem \n");

	return 0;
}

static int hello_release(struct inode *inode, struct file *filep)
{
	printk("hello release() \n");
	up(&sem);
	printk("up sem \n");
	return 0;
}

static ssize_t hello_read(struct file *filep, char __user *buf, size_t size, loff_t *pos)
{
	int error;

	printk("hello read() \n");
	if(size > strlen(kbuf))
		size = strlen(kbuf);

	if(copy_to_user(buf, kbuf, size)) {
		error = -EFAULT;
		return error;
	}
	printk("read: %s \n", buf);
	return size;
}

static ssize_t hello_write(struct file *filep, const char __user *buf, size_t size, loff_t *pos)
{
	int error;

	printk("hello write() \n");
	if(size > KMAX_LEN)
		size = KMAX_LEN;

	memset(kbuf, 0, sizeof(kbuf));
	if(copy_from_user(kbuf, buf, size)) {
		error = -EFAULT;
		return error;
	}
	printk("write: %s \n", kbuf);
	return size;
}

static struct file_operations hello_ops =
{
	.open = hello_open,
	.release = hello_release,
	.read = hello_read,
	.write = hello_write,
};

static int hello_init(void)
{
	int result = 0;

	printk(KERN_ALERT "hello_init \n");
	devno = MKDEV(major, minor);

	result = register_chrdev(major, "test_dev", &hello_ops);
	if(result < 0) {
		printk("register chardev fail \n");
		return result;
	}

	cls = class_create(THIS_MODULE, "hello_cls");
	if(IS_ERR(cls)) {
		printk("class create fail \n");
		result	= PTR_ERR(cls);
		unregister_chrdev(major	, "test_dev");
		return result;
	}

	devno = MKDEV(major, minor);
	class_dev = device_create(cls, NULL, devno, NULL, "hello_dev");
	if(IS_ERR(class_dev)) {
		printk("device create fail \n");
		result	= PTR_ERR(class_dev);
		class_destroy(cls);
	}

	sema_init(&sem, 1); // init

	return 0;
}

static void hello_exit(void)
{
	printk(KERN_ALERT "hello_exit \n");
	device_destroy(cls, devno);
	class_destroy(cls);
	unregister_chrdev(major	, "test_dev");
	return;
}

MODULE_LICENSE("Dual BSD/GPL");

module_init(hello_init);
module_exit(hello_exit);
